package service

import (
	"context"
	"errors"
	"server/global"
	"server/model/appTypes"
	"server/model/databases"
	"server/model/elasticsearch"
	"server/model/other"
	"server/model/request"
	"server/utils"
	"strconv"
	"time"

	"github.com/elastic/go-elasticsearch/v8/typedapi/core/search"
	"github.com/elastic/go-elasticsearch/v8/typedapi/types"
	"github.com/elastic/go-elasticsearch/v8/typedapi/types/enums/scriptlanguage"
	"github.com/elastic/go-elasticsearch/v8/typedapi/types/enums/sortorder"
	"gorm.io/gorm"
)

type ArticleService struct{}

func (articleService *ArticleService) ArticleCreate(req request.ArticleCreate) error {
	b, err := articleService.Exits(req.Title)
	if err != nil {
		return err
	}
	if b {
		return errors.New("the article already exists")
	}

	now := time.Now().Format("2006-01-02 15:04:05")
	articleCreate := elasticsearch.Article{
		CreatedAt: now,
		UpdatedAt: now,
		Cover:     req.Cover,
		Title:     req.Title,
		Keyword:   req.Title,
		Category:  req.Category,
		Tags:      req.Tags,
		Abstract:  req.Abstract,
		Content:   req.Content,
	}
	return global.DB.Transaction(func(tx *gorm.DB) error {
		// 更新文章类别表
		if err = articleService.UpdateCategoryCount(tx, "", articleCreate.Category); err != nil {
			return err
		}
		// 更新文章标签表
		if err = articleService.UpdateTagsCount(tx, []string{}, articleCreate.Tags); err != nil {
			return err
		}
		// 更新图片类别表
		if err = utils.ChangeImagesCategory(tx, []string{articleCreate.Cover}, appTypes.Cover); err != nil {
			return err
		}
		illustrations, err := utils.FindIllustrations(articleCreate.Content)
		if err != nil {
			return err
		}
		if err = utils.ChangeImagesCategory(tx, illustrations, appTypes.Illustration); err != nil {
			return err
		}
		return articleService.Create(&articleCreate)
	})
}

// ArticleUpdate 更新文章
func (articleService *ArticleService) ArticleUpdate(req request.ArticleUpdate) error {
	articleToUpdate := struct {
		UpdatedAt string   `json:"updated_at"`
		Cover     string   `json:"cover"`
		Title     string   `json:"title"`
		Keyword   string   `json:"keyword"`
		Category  string   `json:"category"`
		Tags      []string `json:"tags"`
		Abstract  string   `json:"abstract"`
		Content   string   `json:"content"`
	}{
		UpdatedAt: time.Now().Format("2006-01-02 15:04:05"),
		Cover:     req.Cover,
		Title:     req.Title,
		Keyword:   req.Title,
		Category:  req.Category,
		Tags:      req.Tags,
		Abstract:  req.Abstract,
		Content:   req.Content,
	}
	return global.DB.Transaction(func(tx *gorm.DB) error {
		oldArticle, err := articleService.Get(req.ID)
		if err != nil {
			return err
		}
		// 文章类别
		if err := articleService.UpdateCategoryCount(tx, oldArticle.Category, articleToUpdate.Category); err != nil {
			return err
		}
		// 跟新文章标签表
		if err = articleService.UpdateTagsCount(tx, oldArticle.Tags, articleToUpdate.Tags); err != nil {
			return err
		}
		// 图片类型
		if oldArticle.Cover != articleToUpdate.Cover {
			if err = utils.InitImagesCategory(tx, []string{articleToUpdate.Cover}); err != nil {
				return err
			}
			if err = utils.ChangeImagesCategory(tx, []string{articleToUpdate.Cover}, appTypes.Cover); err != nil {
				return err
			}
		}
		oldIllustrations, err := utils.FindIllustrations(oldArticle.Content)
		if err == nil {
			return err
		}
		newIllustrations, err := utils.FindIllustrations(articleToUpdate.Content)
		if err == nil {
			return err
		}
		addedIllustrations, removedIllustrations := utils.DiffArrays(oldIllustrations, newIllustrations)
		if err = utils.InitImagesCategory(tx, removedIllustrations); err != nil {
			return err
		}
		if err = utils.ChangeImagesCategory(tx, addedIllustrations, appTypes.Illustration); err != nil {
			return err
		}
		return articleService.Update(req.ID, articleToUpdate)
	})
}

func (articleService *ArticleService) ArticleDelete(req request.ArticleDelete) error {
	if len(req.IDs) == 0 {
		return nil
	}
	return global.DB.Transaction(func(tx *gorm.DB) error {
		for _, id := range req.IDs {
			articleDelete, err := articleService.Get(id)
			if err != nil {
				return err
			}
			// 同时更新文章类别表中的数据
			if err = articleService.UpdateCategoryCount(tx, articleDelete.Category, ""); err != nil {
				return err
			}
			// 同时更新文章标签表中的数据
			if err = articleService.UpdateTagsCount(tx, articleDelete.Tags, []string{}); err != nil {
				return err
			}
			// 更新图片类别
			if err = utils.InitImagesCategory(tx, []string{articleDelete.Cover}); err != nil {
				return err
			}
			illustrations, err := utils.FindIllustrations(articleDelete.Content)
			if err == nil {
				return err
			}
			if err = utils.InitImagesCategory(tx, illustrations); err != nil {
				return err
			}
			// 删除评论 todo
		}
		return articleService.Delete(req.IDs)
	})
}

func (articleService *ArticleService) ArticleList(info request.ArticleList) (list interface{}, total int64, err error) {
	req := &search.Request{Query: &types.Query{}}
	boolQuery := &types.BoolQuery{}

	// 按标题查询
	if info.Title != nil {
		boolQuery.Must = append(boolQuery.Must, types.Query{Match: map[string]types.MatchQuery{"title": {Query: *info.Title}}})
	}
	// 根据简介查询
	if info.Abstract != nil {
		boolQuery.Must = append(boolQuery.Must, types.Query{Match: map[string]types.MatchQuery{"abstract": {Query: *info.Abstract}}})
	}
	// 根据类别查询
	if info.Category != nil {
		boolQuery.Filter = []types.Query{
			{Term: map[string]types.TermQuery{"category": {Value: *info.Category}}},
		}
	}
	// 根据条件执行查询请求
	if boolQuery.Must != nil || boolQuery.Filter != nil {
		req.Query.Bool = boolQuery
	} else {
		// 没有筛选条件时查询全部，根据创建时间倒叙
		req.Query.MatchAll = &types.MatchAllQuery{}
		req.Sort = []types.SortCombinations{
			types.SortOptions{SortOptions: map[string]types.FieldSort{"create_at": {Order: &sortorder.Desc}}},
		}
	}

	option := other.EsOption{
		PageInfo: info.PageInfo,
		Index:    elasticsearch.ArticleIndex(),
		Request:  req,
	}

	return utils.EsPagination(context.TODO(), option)
}

func (articleService *ArticleService) ArticleLike(req request.ArticleLike) error {
	return global.DB.Transaction(func(tx *gorm.DB) error {
		var al databases.ArticleLike
		var num int

		// 用户未收藏，则创建收藏记录
		if errors.Is(tx.Where("article_id = ? AND user_id = ?", req.ArticleID, req.UserID).First(&al).Error, gorm.ErrRecordNotFound) {
			if err := tx.Create(&databases.ArticleLike{ArticleID: req.ArticleID, UserID: req.UserID}).Error; err != nil {
				return err
			}
			num = 1
		} else {
			// 已经收藏则取消
			if err := tx.Delete(&al).Error; err != nil {
				return err
			}
			num = -1
		}
		// 更新文章收藏
		source := "ctx._source.likes += " + strconv.Itoa(num)
		script := types.Script{Source: &source, Lang: &scriptlanguage.Painless}
		_, err := global.ESClient.Update(elasticsearch.ArticleIndex(), req.ArticleID).Script(&script).Do(context.TODO())
		return err
	})
}

func (articleService *ArticleService) ArticleIsLike(req request.ArticleLike) (bool, error) {
	return !errors.Is(global.DB.Where("user_id = ? ANd article_id = ?", req.UserID, req.ArticleID).First(&databases.ArticleLike{}).Error, gorm.ErrRecordNotFound), nil
}

func (articleService *ArticleService) ArticleLikesList(info request.ArticleLikesList) (interface{}, int64, error) {
	db := global.DB.Where("user_id = ?", info.UserID)
	option := other.MySQLOption{
		Where:    db,
		PageInfo: info.PageInfo,
	}

	lists, total, err := utils.MySQLPagination(&databases.ArticleLike{}, option)
	if err != nil {
		return nil, 0, err
	}
	var list []struct {
		Id_     string                `json:"_id"`
		Source_ elasticsearch.Article `json:"_source"`
	}

	for _, l := range lists {
		article, err := articleService.Get(l.ArticleID)
		if err != nil {
			return nil, 0, err
		}
		article.UpdatedAt = ""
		article.Content = ""
		article.Keyword = ""
		list = append(list, struct {
			Id_     string                `json:"_id"`
			Source_ elasticsearch.Article `json:"_source"`
		}{
			Id_:     l.ArticleID,
			Source_: article,
		})
	}
	return list, total, err
}
